package com.sangeng.domain;

import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 封装用户信息
 *
 * 继承UserDetails,
 */
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {

    // 封装用户信息
    private User user;

    // 封装权限用户提供的信息,这个是需要被redis缓存的,后续拿到缓存的permissions后我们手动转成authorities用户权限集合
    List<String> permissions;

    public LoginUser(User user, List<String> permissions) {
        this.user = user;
        this.permissions = permissions;
    }

    /**
     * 用户权限集合（SimpleGrantedAuthority 类型）
     *
     * 注意：
     * 1. 使用 @JSONField(serialize = false) 是为了防止 Fastjson 在序列化时包含该字段。
     * 2. 权限字段通常不参与序列化到 Redis 或 Token 中，以避免：
     *    - 敏感权限信息泄露
     *    - 序列化/反序列化异常或兼容性问题 (SimpleGrantedAuthority 是 Spring Security 中常用的权限实现类。
     *                                          它没有为 JSON 序列化做优化，直接序列化可能导致：
     *                                                                          结构复杂、冗余
     *                                                                          反序列化困难
     *                                                                          存储效率低
     *                                   )
     *    - 存储冗余数据，影响性能
     * 3. 每次请求鉴权时会重新从数据库或缓存加载权限，确保权限信息的实时性和安全性。
     */
    @JSONField(serialize = false)
    private Set<SimpleGrantedAuthority> authorities;

    /**
     * 获取权限信息
     *
     * 作用：把权限信息封装成SimpleGrantedAuthority对象,并返回给Spring Security框架。
     *      当框架有需要查看当前登录用户拥有的权限列表时就会调用此方法,比如用@PreAuthorize("hasAuthority('system:dept:list')")注解进行权限控制时会调用此方法。
     *      再比如使用SecurityContextHolder.getContext().setAuthentication(...)把用户权限列表放入上下文时，也会调用此方法。
     *
     * 注意:
     *      是先有 LoginUser 中的权限数据准备好（通过 getAuthorities() 方法），然后在你手动创建 Authentication 对象时传入，也就是说,这个方法是让框架获取权限列表的源头
     *      再调用 SecurityContextHolder.getContext().setAuthentication(...) 才把权限放进上下文。
     *
     * 重点: ( 具体看JwtAuthenticationTokenFilter中封装权限信息到Authentication的代码)
     *       虽然 Redis 中没有存储权限信息authorities(被我们用注解标注不序列化了)，但在 LoginUser.getAuthorities() 方法中做了延迟加载处理
     *       authorities 没有被序列化进 Redis，只要 permissions 字段存在，就能重新生成权限集合。
     *       用户权限从数据库查出，封装为 List<String> permissions
     *       存入 Redis 的 LoginUser 对象中包含这个 permissions 字段（未注解排除）
     *       此时创建 UsernamePasswordAuthenticationToken 时调用 loginUser.getAuthorities(),因为 permissions 还在所以getAuthorities()内部会自动重建权限集合
     *       于是LoginUser中的authorities属性就被赋值了
     *
     *
     * @return authorities 权限信息
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        //把permissions中String类型的权限信息封装成SimpleGrantedAuthority对象(SimpleGrantedAuthority是GrantedAuthority接口的实现类)

        //authorities = new HashSet<>();
        //for (String permission : permissions) {
        //    SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission);
        //    authorities.add( authority);
        //}

        //提前判断权限集合是否为空
        if (authorities != null){
            return authorities;
        }

        //为空则往里填充权限
        authorities = permissions.stream()
                .map(SimpleGrantedAuthority::new)//转化成SimpleGrantedAuthority对象
                .collect(Collectors.toSet());//收集成 Set

        return authorities;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUserName();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }


}
